/*
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * Main.java
 * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.gui;

import weka.classifiers.bayes.net.GUI;
import weka.classifiers.evaluation.ThresholdCurve;
import weka.core.Copyright;
import weka.core.Instances;
import weka.core.Memory;
import weka.core.Option;
import weka.core.OptionHandler;
import weka.core.SelectedTag;
import weka.core.SystemInfo;
import weka.core.Tag;
import weka.core.Utils;
import weka.core.Version;
import weka.core.scripting.Groovy;
import weka.core.scripting.Jython;
import weka.gui.arffviewer.ArffViewerMainPanel;
import weka.gui.beans.KnowledgeFlowApp;
import weka.gui.beans.StartUpListener;
import weka.gui.boundaryvisualizer.BoundaryVisualizer;
import weka.gui.experiment.Experimenter;
import weka.gui.explorer.Explorer;
import weka.gui.graphvisualizer.GraphVisualizer;
import weka.gui.scripting.GroovyPanel;
import weka.gui.scripting.JythonPanel;
import weka.gui.sql.SqlViewer;
import weka.gui.treevisualizer.Node;
import weka.gui.treevisualizer.NodePlace;
import weka.gui.treevisualizer.PlaceNode2;
import weka.gui.treevisualizer.TreeBuild;
import weka.gui.treevisualizer.TreeVisualizer;
import weka.gui.visualize.PlotData2D;
import weka.gui.visualize.ThresholdVisualizePanel;
import weka.gui.visualize.VisualizePanel;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.Reader;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Vector;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JDesktopPane;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTable;
import javax.swing.SwingConstants;
import javax.swing.WindowConstants;
import javax.swing.event.InternalFrameAdapter;
import javax.swing.event.InternalFrameEvent;

/**
 * Menu-based GUI for Weka, replacement for the GUIChooser.
 *
 <!-- options-start -->
 * Valid options are: <p/>
 * 
 * <pre> -gui &lt;MDI|SDI&gt;
 *  Determines the layout of the GUI:
 *  MDI = MDI Layout
 *  SDI = SDI Layout
 *  (default: MDI)</pre>
 * 
 <!-- options-end -->
 *
 * @author  fracpete (fracpete at waikato dot ac dot nz)
 * @version $Revision: 7190 $
 */
public class Main
  extends JFrame
  implements OptionHandler {
  
  /** for serialization. */
  private static final long serialVersionUID = 1453813254824253849L;
  
  /**
   * DesktopPane with background image.
   * 
   * @author  fracpete (fracpete at waikato dot ac dot nz)
   * @version $Revision: 7190 $
   */
  public static class BackgroundDesktopPane
    extends JDesktopPane {
    
    /** for serialization. */
    private static final long serialVersionUID = 2046713123452402745L;
    
    /** the actual background image. */
    protected Image m_Background;
    
    /**
     * intializes the desktop pane.
     * 
     * @param image	the image to use as background
     */
    public BackgroundDesktopPane(String image) {
      super();
      
      try {
	m_Background = Toolkit.getDefaultToolkit().getImage(ClassLoader.getSystemResource(image));
      }
      catch (Exception e) {
	e.printStackTrace();
      }
    }
    
    /**
     * draws the background image.
     * 
     * @param g		the graphics context
     */
    public void paintComponent(Graphics g) {
      super.paintComponent(g);
     
      if (m_Background != null) {
	g.setColor(Color.WHITE);
	g.clearRect(0, 0, getWidth(), getHeight());
	
	int width  = m_Background.getWidth(null);
	int height = m_Background.getHeight(null);
	int x = (getWidth() - width) / 2;
	int y = (getHeight() - height) / 2;
	g.drawImage(m_Background, x, y, width, height, this);
      }
    }
  }
  
  /**
   * Specialized JFrame class.
   * 
   * @author  fracpete (fracpete at waikato dot ac dot nz)
   * @version $Revision: 7190 $
   */
  public static class ChildFrameSDI 
    extends JFrame {
    
    /** for serialization. */
    private static final long serialVersionUID = 8588293938686425618L;
    
    /** the parent frame. */
    protected Main m_Parent;
    
    /**
     * constructs a new internal frame that knows about its parent.
     * 
     * @param parent	the parent frame
     * @param title	the title of the frame
     */
    public ChildFrameSDI(Main parent, String title) {
      super(title);
      
      m_Parent = parent;

      addWindowListener(new WindowAdapter() {
	public void windowActivated(WindowEvent e) {
	  // update title of parent
	  if (getParentFrame() != null)
	    getParentFrame().createTitle(getTitle());
	}
      });
      
      // add to parent
      if (getParentFrame() != null) {
	getParentFrame().addChildFrame(this);
	setIconImage(getParentFrame().getIconImage());
      }
    }
    
    /**
     * returns the parent frame, can be null.
     * 
     * @return		the parent frame
     */
    public Main getParentFrame() {
      return m_Parent;
    }
    
    /**
     * de-registers the child frame with the parent first.
     */
    public void dispose() {
      if (getParentFrame() != null) {
	getParentFrame().removeChildFrame(this);
	getParentFrame().createTitle("");
      }
      
      super.dispose();
    }
  }
  
  /**
   * Specialized JInternalFrame class.
   * 
   * @author  fracpete (fracpete at waikato dot ac dot nz)
   * @version $Revision: 7190 $
   */
  public static class ChildFrameMDI
    extends JInternalFrame {
    
    /** for serialization. */
    private static final long serialVersionUID = 3772573515346899959L;
    
    /** the parent frame. */
    protected Main m_Parent;
    
    /**
     * constructs a new internal frame that knows about its parent.
     * 
     * @param parent	the parent frame
     * @param title	the title of the frame
     */
    public ChildFrameMDI(Main parent, String title) {
      super(title, true, true, true, true);
      
      m_Parent = parent;

      addInternalFrameListener(new InternalFrameAdapter() {
	public void internalFrameActivated(InternalFrameEvent e) {
	  // update title of parent
	  if (getParentFrame() != null)
	    getParentFrame().createTitle(getTitle());
	}
      });
      
      // add to parent
      if (getParentFrame() != null) {
	getParentFrame().addChildFrame(this);
	getParentFrame().jDesktopPane.add(this);
      }
    }
    
    /**
     * returns the parent frame, can be null.
     * 
     * @return		the parent frame
     */
    public Main getParentFrame() {
      return m_Parent;
    }
    
    /**
     * de-registers the child frame with the parent first.
     */
    public void dispose() {
      if (getParentFrame() != null) {
	getParentFrame().removeChildFrame(this);
	getParentFrame().createTitle("");
      }
      
      super.dispose();
    }
  }

  /** displays the GUI as MDI. */
  public final static int GUI_MDI = 0;
  /** displays the GUI as SDI. */
  public final static int GUI_SDI = 1;
  /** GUI tags. */
  public static final Tag[] TAGS_GUI = {
    new Tag(GUI_MDI, "MDI", "MDI Layout"),
    new Tag(GUI_SDI, "SDI", "SDI Layout")
  };
  
  /** the frame itself. */
  protected Main m_Self;
  
  /** the type of GUI to display. */
  protected int m_GUIType = GUI_MDI;
  
  /** variable for the Main class which would be set to null by the memory
   *  monitoring thread to free up some memory if we running out of memory. */
  protected static Main m_MainCommandline;
  
  /** singleton instance of the GUI. */
  protected static Main m_MainSingleton;

  /** list of things to be notified when the startup process of
   *  the KnowledgeFlow is complete. */
  protected static Vector m_StartupListeners = new Vector();

  /** for monitoring the Memory consumption. */
  protected static Memory m_Memory = new Memory(true);
  
  /** contains the child frames (title &lt;-&gt; object). */
  protected HashSet<Container> m_ChildFrames = new HashSet<Container>();

  /** The frame of the LogWindow. */
  protected static LogWindow m_LogWindow = new LogWindow();

  /** filechooser for the TreeVisualizer. */
  protected JFileChooser m_FileChooserTreeVisualizer = new JFileChooser(new File(System.getProperty("user.dir")));

  /** filechooser for the GraphVisualizer. */
  protected JFileChooser m_FileChooserGraphVisualizer = new JFileChooser(new File(System.getProperty("user.dir")));

  /** filechooser for Plots. */
  protected JFileChooser m_FileChooserPlot = new JFileChooser(new File(System.getProperty("user.dir")));

  /** filechooser for ROC curves. */
  protected JFileChooser m_FileChooserROC = new JFileChooser(new File(System.getProperty("user.dir")));
  
  // GUI components
  private JMenu jMenuHelp;
  private JMenu jMenuVisualization;
  private JMenu jMenuTools;
  private JDesktopPane jDesktopPane;
  private JMenu jMenuApplications;
  private JMenuItem jMenuItemHelpSystemInfo;
  private JMenuItem jMenuItemHelpAbout;
  private JMenuItem jMenuItemHelpHomepage;
  private JMenuItem jMenuItemHelpWekaWiki;
  private JMenuItem jMenuItemHelpWekaDoc;
  private JMenuItem jMenuItemHelpSourceforge;
  private JMenuItem jMenuItemVisualizationBoundaryVisualizer;
  private JMenuItem jMenuItemVisualizationGraphVisualizer;
  private JMenuItem jMenuItemVisualizationTreeVisualizer;
  private JMenuItem jMenuItemVisualizationROC;
  private JMenuItem jMenuItemVisualizationPlot;
  private JMenuItem jMenuItemToolsEnsembleLibrary;
  private JMenuItem jMenuItemToolsSqlViewer;
  private JMenuItem jMenuItemToolsGroovyConsole;
  private JMenuItem jMenuItemToolsJythonConsole;
  private JMenuItem jMenuItemToolsArffViewer;
  private JMenuItem jMenuItemApplicationsSimpleCLI;
  private JMenuItem jMenuItemApplicationsKnowledgeFlow;
  private JMenuItem jMenuItemApplicationsExperimenter;
  private JMenuItem jMenuItemApplicationsExplorer;
  private JMenuItem jMenuItemProgramExit;
  private JMenuItem jMenuItemProgramLogWindow;
  private JMenuItem jMenuItemProgramMemoryUsage;
  private JMenuItem jMenuItemProgramPreferences;  // TODO: see below
  private JMenu jMenuProgram;
  private JMenu jMenuExtensions;
  private JMenu jMenuWindows;
  private JMenuBar jMenuBar;
  
  /**
   * default constructor.
   */
  public Main() {
    super();
  }
  
  /**
   * creates a frame (depending on m_GUIType) and returns it.
   * 
   * @param parent		the parent of the generated frame
   * @param title		the title of the frame
   * @param c			the component to place, can be null
   * @param layout		the layout to use, e.g., BorderLayout
   * @param layoutConstraints	the layout constraints, e.g., BorderLayout.CENTER
   * @param width		the width of the frame, ignored if -1
   * @param height		the height of the frame, ignored if -1
   * @param menu		an optional menu
   * @param listener		if true a default listener is added
   * @param visible		if true then the frame is made visible immediately
   * @return			the generated frame
   * @see 			#m_GUIType
   */
  protected Container createFrame(
      Main parent, String title, Component c, LayoutManager layout, 
      Object layoutConstraints, int width, int height, JMenuBar menu,
      boolean listener, boolean visible) {

    Container result = null;
    
    if (m_GUIType == GUI_MDI) {
      final ChildFrameMDI frame = new ChildFrameMDI(parent, title);
      
      // layout
      frame.setLayout(layout);
      if (c != null)
	frame.getContentPane().add(c, layoutConstraints);
      
      // menu
      frame.setJMenuBar(menu);
      
      // size
      frame.pack();
      if ((width > -1) && (height > -1))
	frame.setSize(width, height);
      frame.validate();

      // listener?
      if (listener) {
	frame.addInternalFrameListener(new InternalFrameAdapter() {
	  public void internalFrameClosing(InternalFrameEvent e) {
	    frame.dispose();
	  }
	});
      }
      
      // display frame
      if (visible) {
	frame.setVisible(true);
	try {
	  frame.setSelected(true);
	}
	catch (Exception e) {
	  e.printStackTrace();
	}
      }
      
      result = frame;
    }
    else if (m_GUIType == GUI_SDI) {
      final ChildFrameSDI frame = new ChildFrameSDI(parent, title);
      
      // layout
      frame.setLayout(layout);
      if (c != null)
	frame.getContentPane().add(c, layoutConstraints);
      
      // menu
      frame.setJMenuBar(menu);
      
      // size
      frame.pack();
      if ((width > -1) && (height > -1))
	frame.setSize(width, height);
      frame.validate();

      // location
      int screenHeight = getGraphicsConfiguration().getBounds().height;
      int screenWidth  = getGraphicsConfiguration().getBounds().width;
      frame.setLocation(
	  (screenWidth - frame.getBounds().width) / 2,
	  (screenHeight - frame.getBounds().height) / 2);
      
      // listener?
      if (listener) {
	frame.addWindowListener(new WindowAdapter() {
	  public void windowClosing(WindowEvent e) {
	    frame.dispose();
	  }
	});
      }
      
      // display frame
      if (visible)
	frame.setVisible(true);

      result = frame;
    }
    
    return result;
  }
  
  /**
   * insert the menu item in a sorted fashion.
   * 
   * @param menu	the menu to add the item to
   * @param menuitem	the menu item to add
   */
  protected void insertMenuItem(JMenu menu, JMenuItem menuitem) {
    insertMenuItem(menu, menuitem, 0);
  }
  
  /**
   * insert the menu item in a sorted fashion.
   * 
   * @param menu	the menu to add the item to
   * @param menuitem	the menu item to add
   * @param startIndex	the index in the menu to start with (0-based)
   */
  protected void insertMenuItem(JMenu menu, JMenuItem menuitem, int startIndex) {
    boolean	inserted;
    int		i;
    JMenuItem	current;
    String	currentStr;
    String	newStr;
    
    inserted = false;
    newStr   = menuitem.getText().toLowerCase();
    
    // try to find a spot inbetween
    for (i = startIndex; i < menu.getMenuComponentCount(); i++) {
      if (!(menu.getMenuComponent(i) instanceof JMenuItem))
	continue;
      
      current    = (JMenuItem) menu.getMenuComponent(i);
      currentStr = current.getText().toLowerCase();
      if (currentStr.compareTo(newStr) > 0) {
	inserted = true;
	menu.insert(menuitem, i);
	break;
      }
    }
    
    // add it at the end if not yet inserted
    if (!inserted)
      menu.add(menuitem);
  }
  
  /**
   * initializes the GUI.
   */
  protected void initGUI() {
    m_Self = this;
    
    try {
      // main window
      createTitle("");
      this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
      this.setIconImage(new ImageIcon(getClass().getClassLoader().getResource("weka/gui/weka_icon_new_48.png")).getImage());

      // bits and pieces
      m_FileChooserGraphVisualizer.addChoosableFileFilter(
	  new ExtensionFileFilter(".bif", "BIF Files (*.bif)"));
      m_FileChooserGraphVisualizer.addChoosableFileFilter(
	  new ExtensionFileFilter(".xml", "XML Files (*.xml)"));

      m_FileChooserPlot.addChoosableFileFilter(
	  new ExtensionFileFilter(
	      Instances.FILE_EXTENSION,
	      "ARFF Files (*" + Instances.FILE_EXTENSION + ")"));
      m_FileChooserPlot.setMultiSelectionEnabled(true);

      m_FileChooserROC.addChoosableFileFilter(
	  new ExtensionFileFilter(
	      Instances.FILE_EXTENSION,
	      "ARFF Files (*" + Instances.FILE_EXTENSION + ")"));

      // Desktop
      if (m_GUIType == GUI_MDI) {
	jDesktopPane = new BackgroundDesktopPane("weka/gui/images/weka_background.gif");
	jDesktopPane.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
	setContentPane(jDesktopPane);
      }
      else {
	jDesktopPane = null;
      }

      // Menu
      jMenuBar = new JMenuBar();
      setJMenuBar(jMenuBar);

      // Program
      jMenuProgram = new JMenu();
      jMenuBar.add(jMenuProgram);
      jMenuProgram.setText("Program");
      jMenuProgram.setMnemonic('P');

      // Program/Preferences
      // TODO: read all properties from all props file and display them
      /*
        jMenuItemProgramPreferences = new JMenuItem();
        jMenuProgram.add(jMenuItemProgramPreferences);
        jMenuItemProgramPreferences.setText("Preferences");
        jMenuItemProgramPreferences.setMnemonic('P');
        jMenuItemProgramPreferences.addActionListener(new ActionListener() {
  	  public void actionPerformed(ActionEvent evt) {
	    System.out.println("jMenuItemProgramPreferences.actionPerformed, event="+evt);
	    //TODO add your code for jMenuItemProgramPreferences.actionPerformed
  	  }
        });
       */

      // Program/LogWindow
      jMenuItemProgramLogWindow = new JMenuItem();
      jMenuProgram.add(jMenuItemProgramLogWindow);
      jMenuItemProgramLogWindow.setText("LogWindow");
      jMenuItemProgramLogWindow.setMnemonic('L');
      jMenuItemProgramLogWindow.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent evt) {
	  m_LogWindow.setVisible(true);
	}
      });
      
      jMenuItemProgramMemoryUsage = new JMenuItem();
      jMenuProgram.add(jMenuItemProgramMemoryUsage);
      jMenuItemProgramMemoryUsage.setText("Memory usage");
      jMenuItemProgramMemoryUsage.setMnemonic('M');
      jMenuItemProgramMemoryUsage.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent evt) {
	  String title = jMenuItemProgramMemoryUsage.getText();
	  if (!containsWindow(title)) {
	    final MemoryUsagePanel panel = new MemoryUsagePanel();
	    Container c = createFrame(
		m_Self, title, panel, new BorderLayout(), 
		BorderLayout.CENTER, 400, 50, null, true, true);
	    
	    // optimize size
	    Dimension size = c.getPreferredSize();
	    c.setSize(new Dimension((int) size.getWidth(), (int) size.getHeight()));

	    // stop threads
	    if (m_GUIType == GUI_MDI) {
	      final ChildFrameMDI frame = (ChildFrameMDI) c;
	      Point l = panel.getFrameLocation();
	      if ((l.x != -1) && (l.y != -1))
		frame.setLocation(l);
	      frame.addInternalFrameListener(new InternalFrameAdapter() {
		public void internalFrameClosing(InternalFrameEvent e) {
		  panel.stopMonitoring();
		}
	      });
	    }
	    else {
	      final ChildFrameSDI frame = (ChildFrameSDI) c;
	      Point l = panel.getFrameLocation();
	      if ((l.x != -1) && (l.y != -1))
		frame.setLocation(l);
	      frame.addWindowListener(new WindowAdapter() {
		public void windowClosing(WindowEvent e) {
		  panel.stopMonitoring();
		}
	      });
	    }
	  }
	  else {
	    showWindow(getWindow(title));
	  }
	}
      });

      jMenuProgram.add(new JSeparator());

      // Program/Exit
      jMenuItemProgramExit = new JMenuItem();
      jMenuProgram.add(jMenuItemProgramExit);
      jMenuItemProgramExit.setText("Exit");
      jMenuItemProgramExit.setMnemonic('E');
      jMenuItemProgramExit.addActionListener(new ActionListener() {	
	public void actionPerformed(ActionEvent evt) {
	  // close all children
	  Iterator iter = getWindowList();
	  Vector<Container> list = new Vector<Container>();
	  while (iter.hasNext())
	    list.add((Container) iter.next());
	  for (int i = 0; i < list.size(); i++) {
	    Container c = list.get(i);
	    if (c instanceof ChildFrameMDI)
	      ((ChildFrameMDI) c).dispose();
	    else if (c instanceof ChildFrameSDI)
	      ((ChildFrameSDI) c).dispose();
	  }
	  // close logwindow
	  m_LogWindow.dispose();
	  // close main window
	  m_Self.dispose();
	  // make sure we stop
	  System.exit(0);
	}
      });

      // Applications
      jMenuApplications = new JMenu();
      jMenuBar.add(jMenuApplications);
      jMenuApplications.setText("Applications");
      jMenuApplications.setMnemonic('A');

      // Applications/Explorer
      jMenuItemApplicationsExplorer = new JMenuItem();
      jMenuApplications.add(jMenuItemApplicationsExplorer);
      jMenuItemApplicationsExplorer.setText("Explorer");
      jMenuItemApplicationsExplorer.setMnemonic('E');
      jMenuItemApplicationsExplorer.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent evt) {
	  String title = jMenuItemApplicationsExplorer.getText();
	  if (!containsWindow(title)) {
	    createFrame(
		m_Self, title, new Explorer(), new BorderLayout(), 
		BorderLayout.CENTER, 800, 600, null, true, true);
	  }
	  else {
	    showWindow(getWindow(title));
	  }
	}
      });

      // Applications/Experimenter
      jMenuItemApplicationsExperimenter = new JMenuItem();
      jMenuApplications.add(jMenuItemApplicationsExperimenter);
      jMenuItemApplicationsExperimenter.setText("Experimenter");
      jMenuItemApplicationsExperimenter.setMnemonic('X');
      jMenuItemApplicationsExperimenter.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent evt) {
	  String title = jMenuItemApplicationsExperimenter.getText();
	  if (!containsWindow(title)) {
	    createFrame(
		m_Self, title, new Experimenter(false), new BorderLayout(), 
		BorderLayout.CENTER, 800, 600, null, true, true);
	  }
	  else {
	    showWindow(getWindow(title));
	  }
	}
      });

      // Applications/KnowledgeFlow
      jMenuItemApplicationsKnowledgeFlow = new JMenuItem();
      jMenuApplications.add(jMenuItemApplicationsKnowledgeFlow);
      jMenuItemApplicationsKnowledgeFlow.setText("KnowledgeFlow");
      jMenuItemApplicationsKnowledgeFlow.setMnemonic('K');
      jMenuItemApplicationsKnowledgeFlow.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent evt) {
	  String title = jMenuItemApplicationsKnowledgeFlow.getText();
	  if (!containsWindow(title)) {
	    KnowledgeFlowApp.createSingleton(new String[0]);
	    createFrame(
		m_Self, title, KnowledgeFlowApp.getSingleton(), new BorderLayout(), 
		BorderLayout.CENTER, 900, 600, null, true, true);
	  }
	  else {
	    showWindow(getWindow(title));
	  }
	}
      });

      // Applications/SimpleCLI
      jMenuItemApplicationsSimpleCLI = new JMenuItem();
      jMenuApplications.add(jMenuItemApplicationsSimpleCLI);
      jMenuItemApplicationsSimpleCLI.setText("SimpleCLI");
      jMenuItemApplicationsSimpleCLI.setMnemonic('S');
      jMenuItemApplicationsSimpleCLI.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent evt) {
	  String title = jMenuItemApplicationsSimpleCLI.getText();
	  if (!containsWindow(title)) {
	    try {
	      createFrame(
		  m_Self, title, new SimpleCLIPanel(), new BorderLayout(), 
		  BorderLayout.CENTER, 600, 500, null, true, true);
	    }
	    catch (Exception e) {
	      e.printStackTrace();
	      JOptionPane.showMessageDialog(
		  m_Self, "Error instantiating SimpleCLI:\n" + e.getMessage());
	      return;
	    }
	  }
	  else {
	    showWindow(getWindow(title));
	  }
	}
      });

      // Tools
      jMenuTools = new JMenu();
      jMenuBar.add(jMenuTools);
      jMenuTools.setText("Tools");
      jMenuTools.setMnemonic('T');

      // Tools/ArffViewer
      jMenuItemToolsArffViewer = new JMenuItem();
      jMenuTools.add(jMenuItemToolsArffViewer);
      jMenuItemToolsArffViewer.setText("ArffViewer");
      jMenuItemToolsArffViewer.setMnemonic('A');
      jMenuItemToolsArffViewer.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent evt) {
	  String title = jMenuItemToolsArffViewer.getText();
	  if (!containsWindow(title)) {
	    ArffViewerMainPanel panel = new ArffViewerMainPanel(null);
	    panel.setConfirmExit(false);
	    Container frame = createFrame(
		m_Self, title, panel, new BorderLayout(), 
		BorderLayout.CENTER, 800, 600, panel.getMenu(), true, true);
	    panel.setParent(frame);
	  }
	  else {
	    showWindow(getWindow(title));
	  }
	}
      });

      // Tools/SqlViewer
      jMenuItemToolsSqlViewer = new JMenuItem();
      jMenuTools.add(jMenuItemToolsSqlViewer);
      jMenuItemToolsSqlViewer.setText("SqlViewer");
      jMenuItemToolsSqlViewer.setMnemonic('S');
      jMenuItemToolsSqlViewer.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent evt) {
	  String title = jMenuItemToolsSqlViewer.getText();
	  if (!containsWindow(title)) {
	    final SqlViewer sql = new SqlViewer(null);
	    final Container frame = createFrame(
		m_Self, title, sql, new BorderLayout(), 
		BorderLayout.CENTER, -1, -1, null, false, true);

	    // custom listener
	    if (frame instanceof ChildFrameMDI) {
	      ((ChildFrameMDI) frame).addInternalFrameListener(new InternalFrameAdapter() {
		public void internalFrameClosing(InternalFrameEvent e) {
		  sql.saveSize();
		  ((ChildFrameMDI) frame).dispose();
		}
	      });
	    }
	    else if (frame instanceof ChildFrameSDI) {
	      ((ChildFrameSDI) frame).addWindowListener(new WindowAdapter() {
		public void windowClosing(WindowEvent e) {
		  sql.saveSize();
		  ((ChildFrameSDI) frame).dispose();
		}
	      });
	    }
	  }
	  else {
	    showWindow(getWindow(title));
	  }
	}
      });
      
      // Tools/Bayes net editor
      // Tools/Bayes net editor
      final JMenuItem jMenuItemBayesNet = new JMenuItem();
      jMenuTools.add(jMenuItemBayesNet);
      jMenuItemBayesNet.setText("Bayes net editor");
      jMenuItemBayesNet.setMnemonic('N');

      jMenuItemBayesNet.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          String title = jMenuItemBayesNet.getText();
          
          if (!containsWindow(title)) {
            final GUI bayesNetGUI = new GUI();
            final Container frame = createFrame(
                m_Self, title, bayesNetGUI, new BorderLayout(), 
                BorderLayout.CENTER, 800, 600, bayesNetGUI.getMenuBar(), false, true);
          }
          else {
            showWindow(getWindow(title));
          }          
        }       
      });

      // Tools/Groovy console
      if (Groovy.isPresent()) {
	jMenuItemToolsGroovyConsole = new JMenuItem();
	jMenuTools.add(jMenuItemToolsGroovyConsole);
	jMenuItemToolsGroovyConsole.setText("Groovy console");
	jMenuItemToolsGroovyConsole.setMnemonic('G');
	jMenuItemToolsGroovyConsole.addActionListener(new ActionListener() {
	  public void actionPerformed(ActionEvent evt) {
	    String title = jMenuItemToolsGroovyConsole.getText();
	    if (!containsWindow(title)) {
	      final GroovyPanel panel = new GroovyPanel();
	      final Container frame = createFrame(
		  m_Self, title, panel, new BorderLayout(), 
		  BorderLayout.CENTER, 800, 600, panel.getMenuBar(), false, true);

	      // custom listener
	      if (frame instanceof ChildFrameMDI) {
		((ChildFrameMDI) frame).addInternalFrameListener(new InternalFrameAdapter() {
		  public void internalFrameClosing(InternalFrameEvent e) {
		    ((ChildFrameMDI) frame).dispose();
		  }
		});
	      }
	      else if (frame instanceof ChildFrameSDI) {
		((ChildFrameSDI) frame).addWindowListener(new WindowAdapter() {
		  public void windowClosing(WindowEvent e) {
		    ((ChildFrameSDI) frame).dispose();
		  }
		});
	      }
	    }
	    else {
	      showWindow(getWindow(title));
	    }
	  }
	});
      }

      // Tools/Jython console
      if (Jython.isPresent()) {
	jMenuItemToolsJythonConsole = new JMenuItem();
	jMenuTools.add(jMenuItemToolsJythonConsole);
	jMenuItemToolsJythonConsole.setText("Jython console");
	jMenuItemToolsJythonConsole.setMnemonic('J');
	jMenuItemToolsJythonConsole.addActionListener(new ActionListener() {
	  public void actionPerformed(ActionEvent evt) {
	    String title = jMenuItemToolsJythonConsole.getText();
	    if (!containsWindow(title)) {
	      final JythonPanel panel = new JythonPanel();
	      final Container frame = createFrame(
		  m_Self, title, panel, new BorderLayout(), 
		  BorderLayout.CENTER, 800, 600, panel.getMenuBar(), false, true);

	      // custom listener
	      if (frame instanceof ChildFrameMDI) {
		((ChildFrameMDI) frame).addInternalFrameListener(new InternalFrameAdapter() {
		  public void internalFrameClosing(InternalFrameEvent e) {
		    ((ChildFrameMDI) frame).dispose();
		  }
		});
	      }
	      else if (frame instanceof ChildFrameSDI) {
		((ChildFrameSDI) frame).addWindowListener(new WindowAdapter() {
		  public void windowClosing(WindowEvent e) {
		    ((ChildFrameSDI) frame).dispose();
		  }
		});
	      }
	    }
	    else {
	      showWindow(getWindow(title));
	    }
	  }
      });
      }

      // Tools/EnsembleLibrary
      /* currently disabled due to bugs... FracPete
      jMenuItemToolsEnsembleLibrary = new JMenuItem();
      jMenuTools.add(jMenuItemToolsEnsembleLibrary);
      jMenuItemToolsEnsembleLibrary.setText("EnsembleLibrary");
      jMenuItemToolsEnsembleLibrary.setMnemonic('E');
      jMenuItemToolsEnsembleLibrary.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent evt) {
	  String title = jMenuItemToolsEnsembleLibrary.getText();
	  if (!containsWindow(title)) {
	    EnsembleLibrary value = new EnsembleLibrary();
	    EnsembleLibraryEditor libraryEditor = new EnsembleLibraryEditor();
	    libraryEditor.setValue(value);
	    createFrame(
		m_Self, title, libraryEditor.getCustomEditor(), new BorderLayout(), 
		BorderLayout.CENTER, 800, 600, null, true, true);
	  }
	  else {
	    showWindow(getWindow(title));
	  }
	}
      });
      */

      // Visualization
      jMenuVisualization = new JMenu();
      jMenuBar.add(jMenuVisualization);
      jMenuVisualization.setText("Visualization");
      jMenuVisualization.setMnemonic('V');

      // Visualization/Plot
      jMenuItemVisualizationPlot = new JMenuItem();
      jMenuVisualization.add(jMenuItemVisualizationPlot);
      jMenuItemVisualizationPlot.setText("Plot");
      jMenuItemVisualizationPlot.setMnemonic('P');
      jMenuItemVisualizationPlot.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent evt) {
	  // choose file
	  int retVal = m_FileChooserPlot.showOpenDialog(m_Self);
	  if (retVal != JFileChooser.APPROVE_OPTION)
	    return;

	  // build plot
	  VisualizePanel panel = new VisualizePanel();
	  String filenames = "";
	  File[] files = m_FileChooserPlot.getSelectedFiles();
	  for (int j = 0; j < files.length; j++) {
	    String filename = files[j].getAbsolutePath();
	    if (j > 0)
	      filenames += ", ";
	    filenames += filename;
	    System.err.println("Loading instances from " + filename);
	    try {
	      Reader r = new java.io.BufferedReader(new FileReader(filename));
	      Instances i = new Instances(r);
	      i.setClassIndex(i.numAttributes()-1);
	      PlotData2D pd1 = new PlotData2D(i);

	      if (j == 0) {
		pd1.setPlotName("Master plot");
		panel.setMasterPlot(pd1);
	      } else {
		pd1.setPlotName("Plot "+(j+1));
		pd1.m_useCustomColour = true;
		pd1.m_customColour = (j % 2 == 0) ? Color.red : Color.blue; 
		panel.addPlot(pd1);
	      }
	    }
	    catch (Exception e) {
	      e.printStackTrace();
	      JOptionPane.showMessageDialog(
		  m_Self, "Error loading file '" + files[j] + "':\n" + e.getMessage());
	      return;
	    }
	  }

	  // create frame
	  createFrame(
	      m_Self, jMenuItemVisualizationPlot.getText() + " - " + filenames, 
	      panel, new BorderLayout(), 
	      BorderLayout.CENTER, 800, 600, null, true, true);
	}
      });

      // Visualization/ROC
      // based on this Wiki article:
      // http://weka.sourceforge.net/wiki/index.php/Visualizing_ROC_curve
      jMenuItemVisualizationROC = new JMenuItem();
      jMenuVisualization.add(jMenuItemVisualizationROC);
      jMenuItemVisualizationROC.setText("ROC");
      jMenuItemVisualizationROC.setMnemonic('R');
      jMenuItemVisualizationROC.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent evt) {
	  // choose file
	  int retVal = m_FileChooserROC.showOpenDialog(m_Self);
	  if (retVal != JFileChooser.APPROVE_OPTION)
	    return;

	  // create plot
	  String filename  = m_FileChooserROC.getSelectedFile().getAbsolutePath();
	  Instances result = null;
	  try {
	    result = new Instances(new BufferedReader(new FileReader(filename)));
	  }
	  catch (Exception e) {
	    e.printStackTrace();
	    JOptionPane.showMessageDialog(
		m_Self, "Error loading file '" + filename + "':\n" + e.getMessage());
	    return;
	  }
	  result.setClassIndex(result.numAttributes() - 1);
	  ThresholdVisualizePanel vmc = new ThresholdVisualizePanel();
	  vmc.setROCString("(Area under ROC = " + 
	      Utils.doubleToString(ThresholdCurve.getROCArea(result), 4) + ")");
	  vmc.setName(result.relationName());
	  PlotData2D tempd = new PlotData2D(result);
	  tempd.setPlotName(result.relationName());
	  tempd.addInstanceNumberAttribute();
	  try {
	    vmc.addPlot(tempd);
	  }
	  catch (Exception e) {
	    e.printStackTrace();
	    JOptionPane.showMessageDialog(
		m_Self, "Error adding plot:\n" + e.getMessage());
	    return;
	  }

	  createFrame(
	      m_Self, jMenuItemVisualizationROC.getText() + " - " + filename, 
	      vmc, new BorderLayout(), 
	      BorderLayout.CENTER, 800, 600, null, true, true);
	}
      });

      // Visualization/TreeVisualizer
      jMenuItemVisualizationTreeVisualizer = new JMenuItem();
      jMenuVisualization.add(jMenuItemVisualizationTreeVisualizer);
      jMenuItemVisualizationTreeVisualizer.setText("TreeVisualizer");
      jMenuItemVisualizationTreeVisualizer.setMnemonic('T');
      jMenuItemVisualizationTreeVisualizer.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent evt) {
	  // choose file
	  int retVal = m_FileChooserTreeVisualizer.showOpenDialog(m_Self);
	  if (retVal != JFileChooser.APPROVE_OPTION)
	    return;

	  // build tree
	  String filename = m_FileChooserTreeVisualizer.getSelectedFile().getAbsolutePath();
	  TreeBuild builder = new TreeBuild();
	  Node top = null;
	  NodePlace arrange = new PlaceNode2();
	  try {
	    top = builder.create(new FileReader(filename));
	  }
	  catch (Exception e) {
	    e.printStackTrace();
	    JOptionPane.showMessageDialog(
		m_Self, "Error loading file '" + filename + "':\n" + e.getMessage());
	    return;
	  }

	  // create frame
	  createFrame(
	      m_Self, jMenuItemVisualizationTreeVisualizer.getText() + " - " + filename, 
	      new TreeVisualizer(null, top, arrange), new BorderLayout(), 
	      BorderLayout.CENTER, 800, 600, null, true, true);
	}
      });

      // Visualization/GraphVisualizer
      jMenuItemVisualizationGraphVisualizer = new JMenuItem();
      jMenuVisualization.add(jMenuItemVisualizationGraphVisualizer);
      jMenuItemVisualizationGraphVisualizer.setText("GraphVisualizer");
      jMenuItemVisualizationGraphVisualizer.setMnemonic('G');
      jMenuItemVisualizationGraphVisualizer.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent evt) {
	  // choose file
	  int retVal = m_FileChooserGraphVisualizer.showOpenDialog(m_Self);
	  if (retVal != JFileChooser.APPROVE_OPTION)
	    return;

	  // build graph
	  String filename = m_FileChooserGraphVisualizer.getSelectedFile().getAbsolutePath();
	  GraphVisualizer panel = new GraphVisualizer();
	  try{
	    if (    filename.toLowerCase().endsWith(".xml") 
		|| filename.toLowerCase().endsWith(".bif") ) {
	      panel.readBIF(new FileInputStream(filename));
	    }
	    else {
	      panel.readDOT(new FileReader(filename));
	    }
	  }
	  catch (Exception e) {
	    e.printStackTrace();
	    JOptionPane.showMessageDialog(
		m_Self, "Error loading file '" + filename + "':\n" + e.getMessage());
	    return;
	  }

	  // create frame
	  createFrame(
	      m_Self, jMenuItemVisualizationGraphVisualizer.getText() + " - " + filename, 
	      panel, new BorderLayout(), 
	      BorderLayout.CENTER, 800, 600, null, true, true);
	}
      });

      // Visualization/BoundaryVisualizer
      jMenuItemVisualizationBoundaryVisualizer = new JMenuItem();
      jMenuVisualization.add(jMenuItemVisualizationBoundaryVisualizer);
      jMenuItemVisualizationBoundaryVisualizer.setText("BoundaryVisualizer");
      jMenuItemVisualizationBoundaryVisualizer.setMnemonic('B');
      jMenuItemVisualizationBoundaryVisualizer.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent evt) {
	  String title = jMenuItemVisualizationBoundaryVisualizer.getText();
	  if (!containsWindow(title)) {
	    createFrame(
		m_Self, title, new BoundaryVisualizer(), new BorderLayout(), 
		BorderLayout.CENTER, 800, 600, null, true, true);
	    // dont' do a System.exit after last window got closed!
	    BoundaryVisualizer.setExitIfNoWindowsOpen(false);
	  }
	  else {
	    showWindow(getWindow(title));
	  }
	}
      });

      // Extensions
      jMenuExtensions = new JMenu("Extensions");
      jMenuExtensions.setMnemonic(java.awt.event.KeyEvent.VK_E);
      jMenuBar.add(jMenuExtensions);
      jMenuExtensions.setVisible(false);

      String extensions = GenericObjectEditor.EDITOR_PROPERTIES.getProperty(
	  MainMenuExtension.class.getName(), "");

      if (extensions.length() > 0) {
	jMenuExtensions.setVisible(true);
	String[] classnames = GenericObjectEditor.EDITOR_PROPERTIES.getProperty(
	    MainMenuExtension.class.getName(), "").split(",");
	Hashtable<String,JMenu> submenus = new Hashtable<String,JMenu>();

	// add all extensions
	for (int i = 0; i < classnames.length; i++) {
	  String classname = classnames[i];
	  try {
	    MainMenuExtension ext = (MainMenuExtension) Class.forName(classname).newInstance();

	    // menuitem in a submenu?
	    JMenu submenu = null;
	    if (ext.getSubmenuTitle() != null) {
	      submenu = submenus.get(ext.getSubmenuTitle());
	      if (submenu == null) {
		submenu = new JMenu(ext.getSubmenuTitle());
		submenus.put(ext.getSubmenuTitle(), submenu);
		insertMenuItem(jMenuExtensions, submenu);
	      }
	    }

	    // create menu item
	    JMenuItem menuitem = new JMenuItem();
	    menuitem.setText(ext.getMenuTitle());
	    // does the extension need a frame or does it have its own ActionListener?
	    ActionListener listener = ext.getActionListener(m_Self);
	    if (listener != null) {
	      menuitem.addActionListener(listener);
	    }
	    else {
	      final JMenuItem finalMenuitem = menuitem;
	      final MainMenuExtension finalExt = ext;
	      menuitem.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent e) {
		  Component frame = createFrame(
		      m_Self, finalMenuitem.getText(), 
		      null, null, null, -1, -1, null, false, false);
		  finalExt.fillFrame(frame);
		  frame.setVisible(true);
		}
	      });
	    }

	    // sorted insert of menu item
	    if (submenu != null)
	      insertMenuItem(submenu, menuitem);
	    else
	      insertMenuItem(jMenuExtensions, menuitem);
	  }
	  catch (Exception e) {
	    e.printStackTrace();
	  }
	}
      }

      // Windows
      jMenuWindows = new JMenu("Windows");
      jMenuWindows.setMnemonic(java.awt.event.KeyEvent.VK_W);
      jMenuBar.add(jMenuWindows);
      jMenuWindows.setVisible(false);  // initially, there are no windows open

      // Help
      jMenuHelp = new JMenu();
      jMenuBar.add(jMenuHelp);
      jMenuHelp.setText("Help");
      jMenuHelp.setMnemonic('H');

      // Help/Homepage
      jMenuItemHelpHomepage = new JMenuItem();
      jMenuHelp.add(jMenuItemHelpHomepage);
      jMenuItemHelpHomepage.setText("Weka homepage");
      jMenuItemHelpHomepage.setMnemonic('H');
      jMenuItemHelpHomepage.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent evt) {
	  BrowserHelper.openURL(m_Self, "http://www.cs.waikato.ac.nz/~ml/weka/");
	}
      });

      jMenuHelp.add(new JSeparator());

/*      // Help/WekaDoc
      jMenuItemHelpWekaDoc = new JMenuItem();
      jMenuHelp.add(jMenuItemHelpWekaDoc);
      jMenuItemHelpWekaDoc.setText("Online documentation");
      jMenuItemHelpWekaDoc.setMnemonic('D');
      jMenuItemHelpWekaDoc.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent evt) {
	  BrowserHelper.openURL(m_Self, "http://weka.sourceforge.net/wekadoc/");
	}
      }); */

      // Help/WekaWiki
      jMenuItemHelpWekaWiki = new JMenuItem();
      jMenuHelp.add(jMenuItemHelpWekaWiki);
      jMenuItemHelpWekaWiki.setText("HOWTOs, code snippets, etc.");
      jMenuItemHelpWekaWiki.setMnemonic('W');
      jMenuItemHelpWekaWiki.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent evt) {
	  BrowserHelper.openURL(m_Self, "http://weka.wikispaces.com/");
	}
      });

      // Help/Sourceforge
      jMenuItemHelpSourceforge = new JMenuItem();
      jMenuHelp.add(jMenuItemHelpSourceforge);
      jMenuItemHelpSourceforge.setText("Weka on SourceForge");
      jMenuItemHelpSourceforge.setMnemonic('F');
      jMenuItemHelpSourceforge.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent evt) {
	  BrowserHelper.openURL(m_Self, "http://sourceforge.net/projects/weka/");
	}
      });

      jMenuHelp.add(new JSeparator());

      // Help/SystemInfo
      jMenuItemHelpSystemInfo = new JMenuItem();
      jMenuHelp.add(jMenuItemHelpSystemInfo);
      jMenuItemHelpSystemInfo.setText("SystemInfo");
      jMenuItemHelpHomepage.setMnemonic('S');
      jMenuItemHelpSystemInfo.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent evt) {
	  String title = jMenuItemHelpSystemInfo.getText();
	  if (!containsWindow(title)) {
	    // get info
	    Hashtable info = new SystemInfo().getSystemInfo();

	    // sort names
	    Vector names = new Vector();
	    Enumeration enm = info.keys();
	    while (enm.hasMoreElements())
	      names.add(enm.nextElement());
	    Collections.sort(names);

	    // generate table
	    String[][] data = new String[info.size()][2];
	    for (int i = 0; i < names.size(); i++) {
	      data[i][0] = names.get(i).toString();
	      data[i][1] = info.get(data[i][0]).toString();
	    }
	    String[] titles = new String[]{"Key", "Value"};
	    JTable table = new JTable(data, titles);

	    createFrame(
		m_Self, title, new JScrollPane(table), new BorderLayout(), 
		BorderLayout.CENTER, 800, 600, null, true, true);
	  }
	  else {
	    showWindow(getWindow(title));
	  }
	}
      });

      jMenuHelp.add(new JSeparator());

      // Help/About
      jMenuItemHelpAbout = new JMenuItem();
      jMenuHelp.add(jMenuItemHelpAbout);
      jMenuItemHelpAbout.setText("About");
      jMenuItemHelpAbout.setMnemonic('A');
      jMenuItemHelpAbout.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent evt) {
	  String title = jMenuItemHelpAbout.getText();
	  if (!containsWindow(title)) {
	    JPanel wekaPan = new JPanel();
	    wekaPan.setToolTipText("Weka, a native bird of New Zealand");
	    ImageIcon wii = new ImageIcon(Toolkit.getDefaultToolkit().getImage(ClassLoader.getSystemResource("weka/gui/weka3.gif")));
	    JLabel wekaLab = new JLabel(wii);
	    wekaPan.add(wekaLab);
	    Container frame = createFrame(
		m_Self, title, wekaPan, new BorderLayout(), 
		BorderLayout.CENTER, -1, -1, null, true, true);

	    JPanel titlePan = new JPanel();
	    titlePan.setLayout(new GridLayout(8,1));
	    titlePan.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
	    titlePan.add(new JLabel("Waikato Environment for", SwingConstants.CENTER));
	    titlePan.add(new JLabel("Knowledge Analysis", SwingConstants.CENTER));
	    titlePan.add(new JLabel(""));
	    titlePan.add(new JLabel("Version " + Version.VERSION, SwingConstants.CENTER));
	    titlePan.add(new JLabel(""));
	    titlePan.add(new JLabel("(c) " + Copyright.getFromYear() + " - " + Copyright.getToYear(), SwingConstants.CENTER));
	    titlePan.add(new JLabel(Copyright.getOwner(), SwingConstants.CENTER));
	    titlePan.add(new JLabel(Copyright.getAddress(), SwingConstants.CENTER));

	    if (frame instanceof ChildFrameMDI) {
	      ((ChildFrameMDI) frame).getContentPane().add(titlePan, BorderLayout.NORTH);
	      ((ChildFrameMDI) frame).pack();
	    }
	    else if (frame instanceof ChildFrameSDI) {
	      ((ChildFrameSDI) frame).getContentPane().add(titlePan, BorderLayout.NORTH);
	      ((ChildFrameSDI) frame).pack();
	    }
	  }
	  else {
	    showWindow(getWindow(title));
	  }
	}
      });

      // size + position
      int screenHeight = getGraphicsConfiguration().getBounds().height;
      int screenWidth  = getGraphicsConfiguration().getBounds().width;
      if (m_GUIType == GUI_MDI) {
	int newHeight = (int) (((double) screenHeight) * 0.75);
	int newWidth  = (int) (((double) screenWidth)  * 0.75);
	setSize(
	    1000 > newWidth  ? newWidth  : 1000,
		800  > newHeight ? newHeight : 800);
	setLocation(
	    (screenWidth - getBounds().width) / 2,
	    (screenHeight - getBounds().height) / 2);
      }
      else if (m_GUIType == GUI_SDI) {
	pack();
	setSize(screenWidth, getHeight());
	setLocation(0, 0);
      }
    } 
    catch (Exception e) {
      e.printStackTrace();
    }
  }
  
  /**
   * creates and displays the title.
   * 
   * @param title 	the additional part of the title
   */
  protected void createTitle(String title) {
    String	newTitle;
    
    newTitle = "Weka " + new Version();
    if (title.length() != 0)
      newTitle += " - " + title;
    
    setTitle(newTitle);
  }
  
  /**
   * adds the given child frame to the list of frames.
   * 
   * @param c 		the child frame to add
   */
  public void addChildFrame(Container c) {
    m_ChildFrames.add(c);
    windowListChanged();
  }
  
  /**
   * tries to remove the child frame, it returns true if it could do such.
   * 
   * @param c 		the child frame to remove
   * @return 		true if the child frame could be removed
   */
  public boolean removeChildFrame(Container c) {
    boolean result = m_ChildFrames.remove(c);
    windowListChanged();
    return result;
  }
  
  /**
   * brings child frame to the top.
   * 
   * @param c 		the frame to activate
   * @return 		true if frame was activated
   */
  public boolean showWindow(Container c) {
    boolean        	result;
    ChildFrameMDI	mdiFrame;
    ChildFrameSDI	sdiFrame;
    
    if (c != null) {
      try {
	if (c instanceof ChildFrameMDI) {
	  mdiFrame = (ChildFrameMDI) c;
	  mdiFrame.setIcon(false);
	  mdiFrame.toFront();
	  createTitle(mdiFrame.getTitle());
	}
	else if (c instanceof ChildFrameSDI) {
	  sdiFrame = (ChildFrameSDI) c;
	  sdiFrame.setExtendedState(JFrame.NORMAL);
	  sdiFrame.toFront();
	  createTitle(sdiFrame.getTitle());
	}
      }
      catch (Exception e) {
	e.printStackTrace();
      }
      result = true;
    }
    else {
      result = false;
    }
    
    return result;
  }
  
  /**
   * brings the first frame to the top that is of the specified
   * window class.
   *  
   * @param windowClass	the class to display the first child for
   * @return		true, if a child was found and brought to front
   */
  public boolean showWindow(Class windowClass) {
    return showWindow(getWindow(windowClass));
  }
  
  /**
   * returns all currently open frames.
   * 
   * @return 		an iterator over all currently open frame
   */
  public Iterator getWindowList() {
    return m_ChildFrames.iterator();
  }

  /**
   * returns the first instance of the given window class, null if none can be 
   * found.
   * 
   * @param windowClass	the class to retrieve the first instance for
   * @return		null, if no instance can be found
   */
  public Container getWindow(Class windowClass) {
    Container	result;
    Iterator	iter;
    Container	current;
    
    result = null;
    iter   = getWindowList();
    while (iter.hasNext()) {
      current = (Container) iter.next();
      if (current.getClass() == windowClass) {
        result = current;
        break;
      }
    }
    
    return result;
  }

  /**
   * returns the first window with the given title, null if none can be 
   * found.
   * 
   * @param title	the title to look for
   * @return		null, if no instance can be found
   */
  public Container getWindow(String title) {
    Container	result;
    Iterator	iter;
    Container	current;
    boolean	found;
    
    result = null;
    iter   = getWindowList();
    while (iter.hasNext()) {
      current = (Container) iter.next();
      found   = false;
      
      if (current instanceof ChildFrameMDI)
	found = ((ChildFrameMDI) current).getTitle().equals(title);
      else if (current instanceof ChildFrameSDI)
	found = ((ChildFrameSDI) current).getTitle().equals(title);
	
      if (found) {
        result = current;
        break;
      }
    }
    
    return result;
  }
  
  /**
   * checks, whether an instance of the given window class is already in
   * the Window list.
   * 
   * @param windowClass	the class to check for an instance in the current
   * 			window list
   * @return		true if the class is already listed in the Window list
   */
  public boolean containsWindow(Class windowClass) {
    return (getWindow(windowClass) != null);
  }
  
  /**
   * checks, whether a window with the given title is already in
   * the Window list.
   * 
   * @param title	the title to check for in the current window list
   * @return		true if a window with the given title is already 
   * 			listed in the Window list
   */
  public boolean containsWindow(String title) {
    return (getWindow(title) != null);
  }
  
  /**
   * minimizes all windows.
   */
  public void minimizeWindows() {
    Iterator	iter;
    Container	frame;
    
    iter = getWindowList();
    while (iter.hasNext()) {
      frame = (Container) iter.next();
      try {
	if (frame instanceof ChildFrameMDI)
	  ((ChildFrameMDI) frame).setIcon(true);
	else if (frame instanceof ChildFrameSDI)
	  ((ChildFrameSDI) frame).setExtendedState(JFrame.ICONIFIED);
      }
      catch (Exception e) {
	e.printStackTrace();
      }
    }
  }
  
  /**
   * restores all windows.
   */
  public void restoreWindows() {
    Iterator	iter;
    Container	frame;
    
    iter = getWindowList();
    while (iter.hasNext()) {
      frame = (Container) iter.next();
      try {
	if (frame instanceof ChildFrameMDI)
	  ((ChildFrameMDI) frame).setIcon(false);
	else if (frame instanceof ChildFrameSDI)
	  ((ChildFrameSDI) frame).setExtendedState(JFrame.NORMAL);
    }
      catch (Exception e) {
	e.printStackTrace();
      }
    }
  }
  
  /**
   * is called when window list changed somehow (add or remove).
   */
  public void windowListChanged() {
    createWindowMenu();
  }
  
  /**
   * creates the menu of currently open windows.
   */
  protected synchronized void createWindowMenu() {
    Iterator          iter;
    JMenuItem         menuItem;
    int	              startIndex;
    
    // remove all existing entries
    jMenuWindows.removeAll();
    
    // minimize + restore + separator
    menuItem = new JMenuItem("Minimize");
    menuItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent evt) {
        minimizeWindows();
      }
    });
    jMenuWindows.add(menuItem);
    
    menuItem = new JMenuItem("Restore");
    menuItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent evt) {
        restoreWindows();
      }
    });
    jMenuWindows.add(menuItem);
    
    jMenuWindows.addSeparator();
    
    // windows
    startIndex = jMenuWindows.getMenuComponentCount() - 1;
    iter = getWindowList();
    jMenuWindows.setVisible(iter.hasNext());
    while (iter.hasNext()) {
      Container frame = (Container) iter.next();
      if (frame instanceof ChildFrameMDI)
	menuItem = new JMenuItem(((ChildFrameMDI) frame).getTitle());
      else if (frame instanceof ChildFrameSDI)
	menuItem = new JMenuItem(((ChildFrameSDI) frame).getTitle());
      insertMenuItem(jMenuWindows, menuItem, startIndex);
      menuItem.setActionCommand(Integer.toString(frame.hashCode()));
      menuItem.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent evt) {
          Container frame = null;
          Iterator iter = getWindowList();
          while (iter.hasNext()) {
            frame = (Container) iter.next();
            String hashFrame = Integer.toString(frame.hashCode());
            if (hashFrame.equals(evt.getActionCommand())) {
              showWindow(frame);
              break;
            }
          }
          showWindow(frame);
        }
      });
    }
  }
  
  /**
   * Shows or hides this component depending on the value of parameter b.
   * 
   * @param b		if true, shows this component; otherwise, hides this 
   * 			component
   */
  public void setVisible(boolean b) {
    super.setVisible(b);
    
    if (b)
      paint(this.getGraphics());
  }
  
  /**
   * Create the singleton instance of the Main GUI.
   * 
   * @param args 	commandline options
   */
  public static void createSingleton(String[] args) {
    if (m_MainSingleton == null)
      m_MainSingleton = new Main();
    
    // set options
    try {
      m_MainSingleton.setOptions(args);
    }
    catch (Exception e) {
      e.printStackTrace();
    }

    // notify listeners (if any)
    for (int i = 0; i < m_StartupListeners.size(); i++)
      ((StartUpListener) m_StartupListeners.elementAt(i)).startUpComplete();
  }

  /**
   * Return the singleton instance of the Main GUI.
   *
   * @return the singleton instance
   */
  public static Main getSingleton() {
    return m_MainSingleton;
  }

  /**
   * Add a listener to be notified when startup is complete.
   * 
   * @param s 		a listener to add
   */
  public static void addStartupListener(StartUpListener s) {
    m_StartupListeners.add(s);
  }

  /**
   * Gets an enumeration describing the available options.
   *
   * @return 		an enumeration of all the available options.
   */
  public Enumeration listOptions(){
    Vector        	result;
    String		desc;
    SelectedTag		tag;
    int			i;

    result = new Vector();

    desc  = "";
    for (i = 0; i < TAGS_GUI.length; i++) {
      tag = new SelectedTag(TAGS_GUI[i].getID(), TAGS_GUI);
      desc  +=   "\t" + tag.getSelectedTag().getIDStr() 
      	       + " = " + tag.getSelectedTag().getReadable()
      	       + "\n";
    }
    result.addElement(new Option(
	"\tDetermines the layout of the GUI:\n"
	+ desc
	+ "\t(default: " + new SelectedTag(GUI_MDI, TAGS_GUI) + ")",
	"gui", 1, "-gui " + Tag.toOptionList(TAGS_GUI)));

    return result.elements();
  }
  
  /**
   * returns the options of the current setup.
   *
   * @return		the current options
   */
  public String[] getOptions(){
    Vector<String>    	result;

    result = new Vector();

    result.add("-gui");
    result.add("" + getGUIType());

    return result.toArray(new String[result.size()]);	  
  }

  /**
   * Parses the options for this object. <p/>
   *
   <!-- options-start -->
   * Valid options are: <p/>
   * 
   * <pre> -gui &lt;MDI|SDI&gt;
   *  Determines the layout of the GUI:
   *  MDI = MDI Layout
   *  SDI = SDI Layout
   *  (default: MDI)</pre>
   * 
   <!-- options-end -->
   *
   * @param options	the options to use
   * @throws Exception	if setting of options fails
   */
  public void setOptions(String[] options) throws Exception {
    String	tmpStr;

    tmpStr = Utils.getOption("gui", options);
    if (tmpStr.length() != 0)
      setGUIType(new SelectedTag(tmpStr, TAGS_GUI));
    else
      setGUIType(new SelectedTag(GUI_MDI, TAGS_GUI));
  }

  /**
   * Sets the type of GUI to use. 
   *
   * @param value 	.the GUI type
   */
  public void setGUIType(SelectedTag value) {
    if (value.getTags() == TAGS_GUI) {
      m_GUIType = value.getSelectedTag().getID();
      initGUI();
    }
  }

  /**
   * Gets the currently set type of GUI to display. 
   * 
   * @return 		the current GUI Type.
   */
  public SelectedTag getGUIType() {
    return new SelectedTag(m_GUIType, TAGS_GUI);
  }
  
  /**
   * starts the application.
   * 
   * @param args	the commandline arguments - ignored
   */
  public static void main(String[] args) {
    weka.core.logging.Logger.log(weka.core.logging.Logger.Level.INFO, "Logging started");
    
    LookAndFeel.setLookAndFeel();
    
    try {
      // uncomment the following line to disable the memory management:
      //m_Memory.setEnabled(false);

      // help?
      if (Utils.getFlag('h', args)) {
	System.out.println();
	System.out.println("Help requested.");
	System.out.println();
	System.out.println("General options:");
	System.out.println();
	System.out.println("-h");
	System.out.println("\tprints this help screen");
	System.out.println();

	Enumeration enu = new Main().listOptions();
	while (enu.hasMoreElements()) {
	  Option option = (Option) enu.nextElement();
	  System.out.println(option.synopsis());
	  System.out.println(option.description());
	}

	System.out.println();
	System.exit(0);
      }
      
      // setup splash screen
      Main.addStartupListener(new weka.gui.beans.StartUpListener() {
        public void startUpComplete() {
          m_MainCommandline = Main.getSingleton();
          m_MainCommandline.setVisible(true);
        }
      });
      Main.addStartupListener(new StartUpListener() {
        public void startUpComplete() {
          SplashWindow.disposeSplash();
        }
      });
      SplashWindow.splash(ClassLoader.getSystemResource("weka/gui/images/weka_splash.gif"));

      // start GUI
      final String[] options = (String[]) args.clone();
      Thread nt = new Thread() {
	public void run() {
	  weka.gui.SplashWindow.invokeMethod(
	      Main.class.getName(), "createSingleton", options);
	}
      };
      nt.start();
      
      Thread memMonitor = new Thread() {
	public void run() {
	  while(true) {
	    try {
	      Thread.sleep(4000);
	      System.gc();
	      
	      if (m_Memory.isOutOfMemory()) {
		// clean up
		m_MainCommandline = null;
		System.gc();
		
		// stop threads
		m_Memory.stopThreads();
		
		// display error
		System.err.println("\ndisplayed message:");
		m_Memory.showOutOfMemory();
		System.err.println("\nexiting");
		System.exit(-1);
	      }
	      
	    } catch(InterruptedException ex) { ex.printStackTrace(); }
	  }
	}
      };
      
      memMonitor.setPriority(Thread.MAX_PRIORITY);
      memMonitor.start();
    }
    catch (Exception ex) {
      ex.printStackTrace();
      System.err.println(ex.getMessage());
    }
  }
}
